#include "main.h"
#include "RViz/env_updater.h"

#include <QApplication>
#include <QResource>

#include "client/linux/handler/exception_handler.h"
#include "mainwindow.h"

void createLogFile();
void outputMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg);

static bool ApplicationCrashHandler(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded);

static QString info_log_file_name = "./info-log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm") + ".csv";
static QString crittcal_log_file_name = "./crittcal-log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm") + ".txt";
static QString debug_log_file_name = "./debug-log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm") + ".txt";
static QString log_header(
    "时间,电量,电压,电流,当前路线,当前ID,行驶方向,小车状态,导航模式,ROS导航状态,舵轮设定速度,舵轮设定角度,舵轮实际速度,舵轮实际角度,起点->终点,"
    "导航步骤,计算x,计算y,计算rad,激光x,激光y,激光rad,当前点到目标点距离,点到直线距离,当前点和目标线弧度差,IO输入模块A,IO输入模块B,IO输出模块");
static MainWindow* p = nullptr;      //界面指针
static bool isNeedRecordMsg = true;  //错误信息记录标记位
static QList<QString> criticalMsg;   //错误信息记录

int main(int argc, char* argv[]) {
  if (getenv("env_updated") == nullptr) {
#ifdef __DEBUG__
    std::cout << "\n=== before updateEnv ===\n" << std::endl;
    for (int i = 0; environ[i] != NULL; i++) { std::cout << environ[i] << std::endl; }
#endif
    if (EnvUpdater::updateEnv()) { execve(argv[0], argv, environ); }
    _Exit(-1);
  }
#ifdef __DEBUG__
  std::cout << "\n=== after updateEnv ===\n" << std::endl;
  for (int i = 0; environ[i] != NULL; i++) { std::cout << environ[i] << std::endl; }
#endif

  unsetenv("env_updated");

  ros::init(argc, argv, "qt");
  if (!ros::master::check()) { system("gnome-terminal -x bash -c \"roscore;exec bash\""); }

  QApplication a(argc, argv);

  /************************************** 创建内存共享，检查软件运行是否唯一 **************************************/
  QSharedMemory shared("MainWindow");

  if (shared.attach()) {  // 共享内存被占用则直接返回
    QMessageBox::information(nullptr, QStringLiteral("Warning"), QStringLiteral("Application is alreadly running!"));
    return 0;
  }
  shared.create(1);                        // 共享内存没有被占用则创建UI
  QResource::registerResource("src.rcc");  // 注册rcc文件
  /******************************************** 加载启动动画 ********************************************/
  QPixmap pixmap(":/pic/src/logo/startlogo.png");
  QSplashScreen screen(pixmap);
  screen.show();
  /******************************************** 设置崩溃处理 ********************************************/
  QString appDirPath = QCoreApplication::applicationDirPath() + "/error";  // 设置崩溃处理文件保存路径
  QDir dir;
  if (!dir.exists(appDirPath)) {
    bool res = dir.mkpath(appDirPath);
    qDebug() << "新建文件夹：" << appDirPath << " " << res;
  }
  google_breakpad::MinidumpDescriptor descriptor(QString(appDirPath).toStdString());                      // minidump文件写入到的目录
  google_breakpad::ExceptionHandler eh(descriptor, nullptr, ApplicationCrashHandler, nullptr, true, -1);  // 创建捕捉程序异常对象
  /******************************************** 设置保存日志文件 ********************************************/
  createLogFile();                        // 生成日志文件夹
  qInstallMessageHandler(outputMessage);  // 注册MsgHandler回调函数
  /******************************************** 打开桌面应用 ********************************************/
  MainWindow w(argc, argv);
  p = &w;
  w.initUI();
  w.initData();                //等待所有数据建立完成
  Utility::delayMs(800);       //等待采集错误数据   "注意：使用非阻塞延时，不要阻塞其他线程的运行"
  bool isopen = true;          //判断是否需要打开窗口
  isNeedRecordMsg = false;     //复位错误信息记录标记位
  screen.finish(&w);           //关闭启动动画
  if (!criticalMsg.isEmpty())  //存在错误信息
  {
    QString msg;
    for (int i = 0; i < criticalMsg.size(); i++) { msg += "(" + QString::number(i + 1) + ")" + criticalMsg.at(i) + "\n"; }
    int result = QMessageBox::warning(nullptr, "程序启动注意", "存在以下错误信息：\n" + msg + "是否启动", QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton);
    if (result != QMessageBox::Yes) { isopen = false; }
  }
  if (isopen) {
#ifdef __DEBUG__
    w.show();
#else
    w.showFullScreen();  //启动界面全屏显示 //TODO //全屏显示
#endif
    return a.exec();
  }
  return 0;
}

// 程序崩溃回调函数
static bool ApplicationCrashHandler(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
  Q_UNUSED(context)
  // 程式异常捕获
  qDebug() << context;
  qDebug() << descriptor.fd();
  qDebug() << "into ApplicationCrashHandler";
  if (succeeded) {
    qDebug() << "创建dump文件成功！" << descriptor.path();
  } else {
    qDebug() << "创建dump文件失败！" << descriptor.path();
  }
  // 崩溃处理
  p->MainDeal()->agvhardware()->abortHandler();
  return succeeded;
}

/**
 * @brief outputMessage MsgHandler回调函数,用于向日志文件中输出日志。
 * @param type 信息的类型，{QtDebugMsg，QtInfoMsg，QtWarningMsg，QtCriticalMsg，QtFatalMsg}之一
 * @param context 信息的资料
 * @param msg 信息的文本
 */
void outputMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
  static QMutex mutex;
  mutex.lock();
  QByteArray localMsg = msg.toLocal8Bit();
  QString msg_type;
  switch (type) {
    case QtDebugMsg:
      msg_type = QString("[Debug]");
      fprintf(stderr, "Debug: %s\n", localMsg.constData());
      break;

    case QtInfoMsg:
      msg_type = QString("[Info]");
      fprintf(stderr, "Info: %s\n", localMsg.constData());
      break;

    case QtWarningMsg:
      msg_type = QString("[Warning]");
      fprintf(stderr, "Warning: %s \n", localMsg.constData());
      break;

    case QtCriticalMsg:
      msg_type = QString("[Critical]");
      fprintf(stderr, "Critical: %s \n", localMsg.constData());
      break;

    case QtFatalMsg:
      msg_type = QString("[Fatal]");
      fprintf(stderr, "Fatal: %s \n", localMsg.constData());
      abort();
  }

  logMessage(type, context, msg);

  mutex.unlock();
}

void logMessage(QtMsgType type, const QMessageLogContext&, const QString& msg) {
  static QMutex mutex;
  mutex.lock();

  /***************************info***************************/
  if (type == QtInfoMsg) {
    QFile info_file;
    info_file.setFileName(info_log_file_name);

    if (info_file.size() > 10 * 1024 * 1024)  //超过10M,重新再创建一个文件
    {
      info_log_file_name = "./info-log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm") + ".csv";
      info_file.setFileName(info_log_file_name);
    }

    info_file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream info_text_stream(&info_file);
    if (info_file.size() == 0)  //新文件加首行
    {
      info_text_stream << log_header << "\r\n";
    }
    info_text_stream << QDateTime::currentDateTime().toString("MM/dd hh:mm:ss:zzz") << "," << msg << "\r\n";
    info_file.flush();
    info_file.close();
  }

  /***************************debug***************************/
  if (type == QtDebugMsg) {
    QFile debug_file;
    debug_file.setFileName(debug_log_file_name);

    if (debug_file.size() > 1 * 1024 * 1024)  //超过1M,重新再创建一个文件
    {
      debug_log_file_name = "./debug-log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm") + ".txt";
      debug_file.setFileName(debug_log_file_name);
    }
    debug_file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream debug_text_stream(&debug_file);
    debug_text_stream << QDateTime::currentDateTime().toString("MM/dd hh:mm:ss ") + msg << "\r\n";
    debug_file.flush();
    debug_file.close();
  }

  /***************************crittcal***************************/
  if (type == QtCriticalMsg) {
    if (isNeedRecordMsg) { criticalMsg.append(msg); }
    QFile crittcal_file;
    crittcal_file.setFileName(crittcal_log_file_name);
    if (crittcal_file.size() > 1 * 1024 * 1024)  //超过1M,重新再创建一个文件
    {
      crittcal_log_file_name = "./crittcal-log/" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh_mm") + ".txt";
      crittcal_file.setFileName(crittcal_log_file_name);
    }
    crittcal_file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream crittcal_text_stream(&crittcal_file);
    crittcal_text_stream << QDateTime::currentDateTime().toString("MM/dd hh:mm:ss ") + msg << "\r\n";
    crittcal_file.flush();
    crittcal_file.close();
  }

  mutex.unlock();
}

/**
 * @brief createLogFile 创建存放log文件的文件夹
 */
void createLogFile() {
  QDir* log = new QDir;

  if (!log->exists("./error/")) { log->mkdir("./error/"); }
  if (!log->exists("./ini/")) { log->mkdir("./ini/"); }
  if (!log->exists("./slam-route/")) { log->mkdir("./slam-route/"); }
  if (!log->exists("./slam-point/")) { log->mkdir("./slam-point/"); }
  if (!log->exists("./info-log/")) { log->mkdir("./info-log/"); }
  if (!log->exists("./crittcal-log/")) { log->mkdir("./crittcal-log/"); }
  if (!log->exists("./debug-log/")) { log->mkdir("./debug-log/"); }
}
